using System;
using System.Linq;
using Microsoft.Crm.Workflow;
using System.Workflow.ComponentModel;
using Microsoft.Crm.Sdk.Query;
using System.Workflow.ComponentModel.Compiler;
using Microsoft.Crm.Sdk.Metadata;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy.Metadata;

namespace CrmContrib.Workflow.Activities.Utilities
{
    [CrmWorkflowActivity("Sum Entities by Relationship Activity", "CrmContrib \\ Utilities")]
    public class SumEntitiesByRelationshipActivity : Activity
    {
        // The code to retrieve all many to many items
        // is heavily influenced by http://crm40distributewf.codeplex.com/

        [CrmInput( "Relationship" )]
        [ValidationOption(ValidationOption.Required)]
        public String Relationship
        {
            get { return (string)base.GetValue(RelationshipProperty); }
            set { base.SetValue(RelationshipProperty, value); }
        }

        public static readonly DependencyProperty RelationshipProperty = DependencyProperty.Register("Relationship", typeof(string), typeof(SumEntitiesByRelationshipActivity));

        [CrmInput("SumAttribute")]
        [ValidationOption(ValidationOption.Required)]
        public String SumAttribute
        {
            get { return (string)base.GetValue(SumAttributeProperty); }
            set { base.SetValue(SumAttributeProperty, value); }
        }

        public static readonly DependencyProperty SumAttributeProperty = DependencyProperty.Register("SumAttribute", typeof(string), typeof(SumEntitiesByRelationshipActivity));

        protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext )
        {
            var contextService = executionContext.GetService<IContextService>();
            var workflowContext = contextService.Context;
            var crmService = workflowContext.CreateCrmService();
            var metaDataService = workflowContext.CreateMetadataService();
            HasError = new CrmBoolean( false );

            var metadataRequest = new RetrieveRelationshipRequest { Name = Relationship, RetrieveAsIfPublished = false };
            var metadataResponse = (RetrieveRelationshipResponse)metaDataService.Execute( metadataRequest );

            var request = CreateEntityRequestMessage( workflowContext, metadataResponse.RelationshipMetadata );
            RetrieveMultipleResponse retrieveResponse = (RetrieveMultipleResponse)crmService.Execute( request );
            var entities = retrieveResponse.BusinessEntityCollection.BusinessEntities;

            if ( entities == null )
            {
                HasError = new CrmBoolean( true );
            }
            else
            {
                var amounts = entities.Select( x => ( (DynamicEntity)x ).Properties.Contains( SumAttribute ) ? ( (CrmMoney)( (DynamicEntity)x ).Properties[SumAttribute] ) : new CrmMoney( 0m ) );
                Sum = new CrmMoney( amounts.Sum( x => x.IsNull ? 0 : x.Value ) );
            }

            return base.Execute( executionContext );
        }

        [CrmOutput( "Sum" )]
        public CrmMoney Sum
        {
            get { return (CrmMoney)base.GetValue( SumProperty ); }
            set { base.SetValue( SumProperty, value ); }
        }
        public static readonly DependencyProperty SumProperty = DependencyProperty.Register( "Sum", typeof( CrmMoney ), typeof( SumEntitiesByRelationshipActivity ) );

        [CrmOutput( "Has an Error" )]
        public CrmBoolean HasError
        {
            get { return (CrmBoolean)base.GetValue( HasErrorProperty ); }
            set { base.SetValue( HasErrorProperty, value ); }
        }

        public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register( "HasError", typeof( CrmBoolean ), typeof( SumEntitiesByRelationshipActivity ) );

        private RetrieveMultipleRequest CreateEntityRequestMessage( IWorkflowContext context, RelationshipMetadata metadata )
        {
            QueryBase query; 
            switch ( metadata.RelationshipType )
            {
                case EntityRelationshipType.ManyToMany:
                    query = CreateManyToManyQuery( context, metadata as ManyToManyMetadata );
                    break;
                case EntityRelationshipType.OneToMany:
                    query = CreateOneToManyQuery( context, metadata as OneToManyMetadata );
                    break;
                default:
                    throw new ArgumentException("Unknown Entity Relationship type", "relationshipType");
            }

            var projection = new ColumnSet();
            projection.AddColumn( SumAttribute );
            query.ColumnSet = projection;

            var request = new RetrieveMultipleRequest { Query = query, ReturnDynamicEntities = true };
            return request;
        }

        private QueryExpression CreateManyToManyQuery( IWorkflowContext context, ManyToManyMetadata relationship )
        {
            string primaryAttribute, secondaryEntity, secondaryAttribute;
            string intersection = relationship.IntersectEntityName;
            if ( relationship.Entity1LogicalName == context.PrimaryEntityName )
            {
                // entity1 is primary
                primaryAttribute = relationship.Entity1IntersectAttribute;
                secondaryEntity = relationship.Entity2LogicalName;
                secondaryAttribute = relationship.Entity2IntersectAttribute;
            }
            else
            {
                // entity2 is primary
                primaryAttribute = relationship.Entity2IntersectAttribute;
                secondaryEntity = relationship.Entity1LogicalName;
                secondaryAttribute = relationship.Entity1IntersectAttribute;
            }

            // First link
            var secondaryToIntersection = new LinkEntity();
            secondaryToIntersection.LinkToEntityName = intersection;
            secondaryToIntersection.LinkFromAttributeName =
            secondaryToIntersection.LinkToAttributeName = secondaryAttribute;

            // Second link
            LinkEntity intersectionToPrimary = new LinkEntity();
            intersectionToPrimary.LinkToEntityName = context.PrimaryEntityName;
            intersectionToPrimary.LinkFromAttributeName =
            intersectionToPrimary.LinkToAttributeName = primaryAttribute;

            // Condition
            ConditionExpression primaryCondition = new ConditionExpression { AttributeName = primaryAttribute, 
                                                                             Operator = ConditionOperator.Equal,
                                                                             Values = new object[] { context.PrimaryEntityId.ToString() }
            };

            // Chain all links
            secondaryToIntersection.LinkEntities.Add( intersectionToPrimary );
            intersectionToPrimary.LinkCriteria.Conditions.Add( primaryCondition );

            QueryExpression query = new QueryExpression { EntityName = secondaryEntity };
            query.LinkEntities.Add( secondaryToIntersection );
            return query;
        }

        private QueryByAttribute CreateOneToManyQuery( IWorkflowContext context, OneToManyMetadata relationship )
        {
            var query = new QueryByAttribute { EntityName = relationship.ReferencingEntity, 
                                               Attributes = new string[] { relationship.ReferencingAttribute }, 
                                               Values = new string[] { context.PrimaryEntityId.ToString() } };
            return query;
        }
    }
}
